/**
 * @author David Young
 */
package csab.core;

import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;

import csab.core.utility.Observable;
import csab.game.DumbController;

/**
 * This is renderable game object which is meant to represent
 * a controlled character in the game.
 * @author David Young
 */
public abstract class Sprite extends Collidable implements Observable {
	private boolean canDie;
	private BehaviorController controller;
	private int healthpoints;
	private Inventory inventory;
	private boolean isDead;
	private boolean isJumping;
	private int jumpSpeed;
	private int maxhealth;
	private int moveSpeed;
	private int dmgfallvel;
	private int falldamage;
	private boolean direct;
	protected boolean readytoattack;
	protected boolean isattacking;
	private boolean needstoclean;
	
	public static final int DEFAULT_MOVE_SPEED = 100;
	public static final int DEFAULT_JUMP_SPEED = 200;
	
	public static final int DEFAULT_DMG_FALL_VELOCITY = 300;
	private static final int DAMAGE_FLASH_PERIOD = 700;
	private static final int DAMAGE_SWAP_FLASH_PERIOD = 200;
	private static final Color DAMAGE_COLOR = new Color(255, 0, 0, 0.75f);
	
	private static final int DEATH_PERIOD = 5000;
	private static final Color DEATH_COLOR = new Color(0,255,0, 0.8f);
	
	private boolean handleDeath;
	private int deathFreeze;
	
	protected boolean isDamaged;
	private int lastdamageupdate;
	private int totaldamageupdate;
	private Color damagecol;
	
	protected Animation animAttackLeft;
	protected Animation animAttackRight;
	protected Animation animLeftWalk;
	protected Animation animRightWalk;
	protected Animation animLeftIdle;
	protected Animation animRightIdle;
	protected Animation animFwdIdle;
	protected Animation animDeath;
	private Animation current;
	
	/* (non-Javadoc)
	 * @see csab.core.Entity#draw(int, int)
	 */
	@Override
	public void draw(int x, int y, Graphics g) {
		if(!isDead)
		{
			if(!isDamaged || damagecol == null)
				this.current.draw(x, y - 1);
			else
			{
				this.current.draw(x, y - 1, damagecol);
			}
		}
		else
			this.animDeath.draw(x, y - 1, DEATH_COLOR);
	}
	
	public abstract boolean canAttack();
	public abstract void prepareAttack();
	public void finishAttacking()
	{
		if(direct)
			current = this.animRightIdle;
		else
			current = this.animLeftIdle;
	}
	public void doAttack()
	{
		if(direct)
			current = this.animAttackRight;
		else
			current = this.animAttackLeft;
	}
	public boolean readyToAttack()
	{
		return this.readytoattack;
	}
	public boolean isAttacking()
	{
		return this.isattacking;
	}
	
	private void init()
	{
		damagecol = DAMAGE_COLOR;
		totaldamageupdate = 0;
		lastdamageupdate = 0;
		isDamaged = false;
		falldamage = 50;
		handleDeath = false;
		deathFreeze = 0;
		readytoattack = false;
		isattacking = false;
		direct = true;
		
		needstoclean = false;
		
		jumpSpeed = DEFAULT_JUMP_SPEED;
		moveSpeed = DEFAULT_MOVE_SPEED;
		
		healthpoints = 100;
		isDead = false;
		canDie = true;
		maxhealth = 100;
		controller = new DumbController(this);
		dmgfallvel = DEFAULT_DMG_FALL_VELOCITY;
		current = null;
	}
	
	/**
	 * Set the amount of damage this object experiences
	 * when it reaches the minimum fall damage velocity.
	 * @return The amount of damage this object would
	 * receive.
	 */
	public int getFallDamage()
	{
		return falldamage;
	}
	
	/**
	 * Set the amount of damage this object experiences
	 * when it reaches the minimum fall damage velocity.
	 * @param fdmg The new fall damage value.
	 */
	public void setFallDamage(int fdmg)
	{
		falldamage = fdmg;
	}
	
	/**
	 * Set the minimum fall velocity that must be
	 * achieved for this object to experience fall 
	 * damage.
	 * @param fallv The new minimum fall velocity.
	 */
	public void setDmgFallVel(int fallv)
	{
		dmgfallvel = Math.abs(fallv);
	}
	
	/**
	 * Return the minimum fall velocity that must be
	 * achieved for the object to experience fall
	 * damage.
	 * @return The minimum fall velocity.
	 */
	public int getDmgFallVel()
	{
		return dmgfallvel;
	}
	
	protected void initAnims(Animation leftw, Animation rightw, Animation leftidle, Animation rightidle, 
							 Animation fwdidle, Animation death, Animation atl, Animation atr)
	{
		this.animLeftWalk = leftw;
		this.animRightWalk = rightw;
		this.animLeftIdle = leftidle;
		this.animRightIdle = rightidle;
		this.animFwdIdle = fwdidle;
		this.animDeath = death;
		this.animAttackLeft = atl;
		this.animAttackRight = atr;
		this.current = rightidle;
	}
	
	/**
	 * The constructor for this class.
	 */
	public Sprite(Animation leftw, Animation rightw, Animation leftidle, Animation rightidle, Animation fwdidle, Animation death,Animation atl, Animation atr)
	{
		super("Sprite"+(Entity.getEntityCount()-1));
		initAnims(leftw, rightw, leftidle, rightidle, fwdidle, death,atl,atr);
		init();
	}
	
	/**
	 * The constructor for this class.
	 */
	public Sprite(String name, Animation leftw, Animation rightw, Animation leftidle, Animation rightidle, Animation fwdidle, Animation death,Animation atl, Animation atr)
	{
		super(name);
		initAnims(leftw, rightw, leftidle, rightidle, fwdidle, death,atl,atr);
		init();
	}
	
	/**
	 * The constructor for this class.
	 */
	public Sprite(String name)
	{
		super(name);
		init();
	}
	
	/**
	 * The constructor for this class.
	 */
	public Sprite()
	{
		super("Sprite"+(Entity.getEntityCount()-1));
		init();
	}
	
	/**
	 * This is called every update in order to
	 * perform any neccesary per-update actions that 
	 * this object needs to perform in order to behave 
	 * correctly.
	 */
	public void doAction(int delta)
	{
		if(handleDeath)
		{
			animDeath.setAutoUpdate(true);
			animDeath.setLooping(false);
			animDeath.restart();
			animDeath.start();
			handleDeath = false;
		}
		else if(isDead)
		{
			deathFreeze += delta;
			
			if(deathFreeze >= DEATH_PERIOD)
				needstoclean = true;
		}
		else if(isDamaged)
		{
			totaldamageupdate +=delta;
			lastdamageupdate +=delta;
			
			if(totaldamageupdate > DAMAGE_FLASH_PERIOD)
			{
				isDamaged = false;
				totaldamageupdate = 0;
				lastdamageupdate = 0;
				damagecol = DAMAGE_COLOR;
			}
			else if(lastdamageupdate > DAMAGE_SWAP_FLASH_PERIOD)
			{
				lastdamageupdate = 0;
				if(damagecol == DAMAGE_COLOR)
					damagecol = null;
				else
					damagecol = DAMAGE_COLOR;
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see csab.core.Collidable#doCollide(csab.core.Collidable)
	 */
	@Override
	public void doCollide(Collidable obj) {
		if(obj instanceof Item)
			inventory.addItem((Item) obj);
	}
	
	/**
	 * Deal a set amount of damage to this Sprite.
	 * @param dmg The amount of damage to do.
	 */
	public void doDamage(int dmg)
	{
		if(canDie && !isDead && !isDamaged)
		{
			isDamaged = true;
			healthpoints = healthpoints - dmg;
			healthpoints = Math.max(healthpoints, 0);
			if(healthpoints <= 0)
			{
				isDead = true;
				handleDeath = true;
			}
		}
	}
	
	/**
	 * Get the BehaviorController of this Sprite.
	 * @return This Sprite's BehaviorController
	 * @see csab.core.BehaviorController
	 */
	public BehaviorController getController()
	{
		return controller;
	}
	
	/**
	 * Get the current health value of this Sprite.
	 * @return The health value of this Sprite.
	 */
	public int getHealth()
	{
		return healthpoints;
	}
	
	/**
	 * Get the y-axis movement speed of the
	 * jump method. 
	 * @return The y-axis movement speed of
	 * the jump method.
	 */
	public int getJumpSpeed() {
		return jumpSpeed;
	}
	
	/**
	 * Get the maximum health value of this Sprite.
	 * @return The maximum health of this Sprite.
	 */
	public int getMaxHealth()
	{
		return maxhealth;
	}
	
	
	/**
	 * Get the x-axis movement speed of the
	 * moveLeft and moveRight methods.
	 * @return The x-axis movement speed of
	 * the moveLeft and moveRight methods.
	 */
	public int getMoveSpeed() {
		return moveSpeed;
	}
	
	/**
	 * Set this object into an idle state and allow
	 * it to perform any actions required to fully
	 * express its "idleness". This makes the object
	 * idle using its' forward Animation.
	 */
	public void idle() {
		this.setVX(0);
		current = animFwdIdle;
	}
	
	/**
	 * Make this object stop moving (idles in the direction
	 * of last travel).
	 */
	public void stopMoving() {
		this.setVX(0);
		if(current == animLeftWalk || current == animLeftIdle)
			current = animLeftIdle;
		else if(current == animRightWalk || current == animRightIdle)
			current = animRightIdle;
	}
	
	/**
	 * Get whether or not this object is dead.
	 * @return true if this object is dead.
	 */
	public boolean isDead() {
		return isDead;
	}
	
	/**
	 * Make this object perform a jump.
	 */
	public void jump() {
		if(!isJumping)
		{
			this.setVY(-getJumpSpeed());
			if(current == animLeftWalk || current == animLeftIdle)
				current = animLeftIdle;
			else if(current == animRightWalk || current == animRightIdle)
				current = animRightIdle;
			else
				current = animFwdIdle;
			isJumping = true;
		}
	}
	
	/**
	 * Determine whether or not this object is
	 * currently jumping.
	 * @return true if this object is jumping.
	 */
	public boolean isJumping()
	{
		return isJumping;
	}
	
	/**
	 * Tell this Sprite that it has landed (no longer jumping).
	 */
	public void landed() {
		isJumping = false;
	}
	
	/**
	 * Make this object move in a leftwards direction.
	 */
	public void moveLeft() {
		this.setVX(-getMoveSpeed());
		direct = false;
		current = animLeftWalk;
	}
	
	/**
	 * Make this object move in a rightwards direction.
	 */
	public void moveRight() {
		this.setVX(getMoveSpeed());
		direct = true;
		current = animRightWalk;
	}
	
	/**
	 * Set whether or not this object is killable/
	 * damageable.
	 * @param val Whether or not this object can be killed.
	 */
	public void setCanDie(boolean val)
	{
		canDie = true;
	}
	
	/**
	 * Set the BehaviorController class of this Sprite. This Sprite
	 * takes control of the controller.
	 * @param controller The new controller for this Sprite.
	 * @see csab.core.BehaviorController
	 */
	public void setController(BehaviorController controller)
	{
		this.controller = controller;
	}

	/**
	 * Add some health to this Sprite (takes the
	 * absolute value of the health parameter).
	 * @param health The new health value of this Sprite.
	 */
	public void giveHealth(int health)
	{
		if(health > 0)
			isDead = false;
		healthpoints = Math.min(Math.abs(health) + healthpoints, maxhealth);
	}

	/**
	 * Set the y-axis movement speed of the jump method.
	 * @param speed The new speed with which y-axis
	 * movement is conducted by the jump method.
	 */
	public void setJumpSpeed(int speed) {
		jumpSpeed = speed;
	}

	/**
	 * Set the maximum health value of this Sprite.
	 * @param mh The new max health value.
	 */
	public void setMaxHealth(int mh)
	{
		maxhealth = mh;
	}

	/**
	 * Set the x-axis movement speed of the moveLeft
	 * and moveRight methods.
	 * @param speed The new speed with which x-axis
	 * movement is conducted by the moveLeft and moveRight 
	 * methods.
	 */
	public void setMoveSpeed(int speed) {
		moveSpeed = speed;
	}

	/**
	 * @return
	 */
	public boolean needsCleanup() {
		return needstoclean;
	}
}
